#ifndef XG_PYTHONLOADER_H
#define XG_PYTHONLOADER_H
/////////////////////////////////////////////////////////////////////

extern "C" {
#ifdef _MSC_VER
#define MS_NO_COREDLL
#ifndef PyInt_AsLong
#pragma comment(lib, "python36.lib")
#else
#pragma comment(lib, "python27.lib")
#endif
#endif

#include <Python.h>

typedef PyObject* PythonHandle;

#ifndef PyInt_AsLong
#define PyInt_AsLong PyLong_AsLong
#endif

#ifndef PyInt_FromLong
#define PyInt_FromLong PyLong_FromLong
#endif

#ifndef PyString_AsString
#define PyString_AsString _PyUnicode_AsString
#endif

#ifndef PyString_FromString
#define PyString_FromString PyUnicode_FromString
#endif
}

#include "../stdx/std.h"

#define PYTHON_MOUDLE_INIT(name)												\
static void __module_define__();												\
static PyMethodDef __methods__[100];											\
static void python_export(const char* name, PyCFunction func)					\
{static int idx = 0; __methods__[idx++] = {name, func, METH_VARARGS, NULL};}	\
PyMODINIT_FUNC PyInit_##name(){													\
static PyModuleDef m = {PyModuleDef_HEAD_INIT, #name, NULL, -1, __methods__};	\
memset(__methods__, 0, sizeof(__methods__)); __module_define__();				\
return PyModule_Create(&m);} void __module_define__()

class PythonLocker : public Object
{
private:
	PyGILState_STATE lock;

public:
	PythonLocker()
	{
		lock = PyGILState_Ensure();
	}
	~PythonLocker()
	{
		PyGILState_Release(lock);
	}
};

class PythonLoader : public Object
{
protected:
	PythonHandle handle;

	PythonLoader(const PythonLoader&);
	PythonLoader& operator = (const PythonLoader&);

	bool addRef()
	{
		CHECK_FALSE_RETURN(handle);
		Py_INCREF(handle);

		return true;
	}
	bool subRef()
	{
		CHECK_FALSE_RETURN(handle);
		Py_DECREF(handle);
		
		return true;
	}
	PythonLoader(PythonHandle handle)
	{
		this->handle = handle;
	}
	bool hasObject(const string& name) const
	{
		return PyObject_HasAttrString(handle, name.c_str()) ? true : false;
	}
	bool setItem(int idx, PythonHandle obj)
	{
		CHECK_FALSE_RETURN(obj);

		if (idx >= 0)
		{
			PyList_SetItem(handle, idx, obj);
		}
		else
		{
			PyList_Append(handle, obj);
			Py_DECREF(obj);
		}

		return true;
	}
	PythonHandle getObject(const string& name) const
	{
		return PyObject_GetAttrString(handle, name.c_str());
	}
	bool setItem(const string& key, PythonHandle obj)
	{
		CHECK_FALSE_RETURN(obj);

		PyDict_SetItemString(handle, key.c_str(), obj);
		Py_DECREF(obj);
		
		return true;
	}
	bool setVariable(const string& name, PythonHandle obj)
	{
		CHECK_FALSE_RETURN(obj);

		PyObject_SetAttrString(handle, name.c_str(), obj);
		Py_DECREF(obj);

		return true;
	}
	PythonLoader& operator = (const PythonHandle& handle)
	{
		assert(this->handle == NULL);

		this->handle = handle;

		return *this;
	}

public:
	PythonLoader()
	{
		handle = NULL;
	}
	~PythonLoader()
	{
		destroy();
	}
	static void Setup(bool multithread = false)
	{
		if (Py_IsInitialized() == 0)
		{
			Py_Initialize();
			
			if (multithread)
			{
				PyEval_InitThreads();
				PyEval_ReleaseThread(PyThreadState_Get());
			}
		}
	}
	static void Shutdown(bool multithread = false)
	{
		if (Py_IsInitialized())
		{
			if (multithread) PyGILState_Ensure();

			Py_Finalize();
		}
	}

public:
	void close()
	{
		destroy();
	}
	void destroy()
	{
		if (subRef()) handle = NULL;
	}
	bool canUse() const
	{
		return handle ? true : false;
	}
	PythonHandle getHandle() const
	{
		return handle;
	}
	string addPath(const string& filepath)
	{
		if (filepath.empty()) return filepath;

		string path = filepath;
		string name = path::name(filepath);

		if (name.length() > 3)
		{
			string ext = name.substr(name.length() - 3);

			if (stdx::tolower(ext) == ".py")
			{
				name = name.substr(0, name.length() - 3);
				path = path::parent(filepath);
			}
		}

		if (filepath.empty()) return name;

		static set<string> pset;

		path = stdx::replace(path, "\\", "/");

		if (pset.empty()) PyRun_SimpleString("import sys");

		if (pset.insert(path).second)
		{
			path = "sys.path.append('" + path + "')";
			PyRun_SimpleString(path.c_str());
		}

		return name;
	}
	bool import(const string& filename)
	{
		destroy();

		handle = PyImport_ImportModule(addPath(filename).c_str());

		return canUse();
	}
	bool loadFile(const string& filename)
	{
		string msg;

		CHECK_FALSE_RETURN(stdx::GetFileContent(msg, filename));

		return loadString(msg, addPath(filename));
	}
	bool loadString(const string& code, const string& name = "xg")
	{
		destroy();

		PythonLoader co = Py_CompileString(code.c_str(), "PYTHON COMPILE", Py_file_input);

		if (co.canUse()) handle = PyImport_ExecCodeModule((char*)(name.c_str()), co.getHandle());

		return canUse();
	}
	bool init(const string& filename = "", bool flag = true)
	{
		return flag ? loadFile(filename) : loadString(filename);
	}

public:
	int size() const
	{
		if (isList()) return PyList_Size(handle);
		if (isDict()) return PyDict_Size(handle);
		if (isTuple()) return PyTuple_Size(handle);

		return -1;
	}
	bool isList() const
	{
		return strcmp(handle->ob_type->tp_name, "list") == 0;
	}
	bool isDict() const
	{
		return strcmp(handle->ob_type->tp_name, "dict") == 0;
	}
	bool isTuple() const
	{
		return strcmp(handle->ob_type->tp_name, "tuple") == 0;
	}
	bool isBytes() const
	{
		return strcmp(handle->ob_type->tp_name, "bytes") == 0;
	}
	bool isDouble() const
	{
		return strcmp(handle->ob_type->tp_name, "float") == 0;
	}
	bool isNumber() const
	{
		return isDouble() || isInteger();
	}
	bool isString() const
	{
		return strcmp(handle->ob_type->tp_name, "str") == 0;
	}
	bool isInteger() const
	{
		return strcmp(handle->ob_type->tp_name, "int") == 0 || strcmp(handle->ob_type->tp_name, "long") == 0;
	}
	bool isBoolean() const
	{
		return strcmp(handle->ob_type->tp_name, "bool") == 0;
	}
	bool isFunction() const
	{
		return strcmp(handle->ob_type->tp_name, "function") == 0;
	}
	int getInteger() const
	{
		return (int)(isDouble() ? PyFloat_AsDouble(handle) : PyInt_AsLong(handle));
	}
	bool getBoolean() const
	{
		return PyObject_IsTrue(handle) ? true : false;
	}
	double getDouble() const
	{
		return PyFloat_AsDouble(handle);
	}
	string getString() const
	{
		if (handle == NULL) return stdx::EmptyString();

		const char* str = PyString_AsString(handle);

		if (str) return str;

		if (isDouble()) return stdx::str(getDouble());
		if (isInteger()) return stdx::str(getInteger());
		if (isBoolean()) return getBoolean() ? "true" : "false";

		return stdx::EmptyString();
	}
	SmartBuffer getBuffer() const
	{
		char* str = NULL;
		Py_ssize_t sz = 0;

		if (isString())
		{
			str = PyString_AsString(handle);

			if (str) sz = strlen(str);
		}
		else
		{
			PyBytes_AsStringAndSize(handle, &str, &sz);
		}

		if (str == NULL && sz <= 0) return SmartBuffer();

		SmartBuffer data(sz);

		memcpy(data.str(), str, sz);

		return data;
	}
	string getItem(int idx) const
	{
		PythonLoader obj;

		return getItem(idx, obj) ? obj.getString() : stdx::EmptyString();
	}
	bool getItem(int idx, int& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(idx, obj));

		val = obj.getInteger();

		return true;
	}
	bool getItem(int idx, bool& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(idx, obj));

		val = obj.getBoolean();

		return true;
	}
	bool getItem(int idx, double& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(idx, obj));

		val = obj.getDouble();

		return true;
	}
	bool getItem(int idx, string& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(idx, obj));

		val = obj.getString();

		return true;
	}
	bool getItem(int idx, SmartBuffer& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(idx, obj));

		val = obj.getBuffer();

		return true;
	}
	bool getItem(int idx, PythonLoader& val) const
	{
		val = isTuple() ? PyTuple_GetItem(handle, idx) : PyList_GetItem(handle, idx);

		return val.addRef();
	}
	bool setItem(int idx, int val)
	{
		return setItem(idx, PyInt_FromLong(val));
	}
	bool setItem(int idx, bool val)
	{
		return setItem(idx, PyBool_FromLong(val ? 1 : 0));
	}
	bool setItem(int idx, double val)
	{
		return setItem(idx, PyFloat_FromDouble(val));
	}
	bool setItem(int idx, const string& val)
	{
		return setItem(idx, val.c_str(), val.length());
	}
	bool setItem(int idx, const char* val, int sz = 0)
	{
		if (sz <= 0) sz = strlen(val);

		return setItem(idx, PyByteArray_FromStringAndSize(val, sz));
	}
	string getItem(const string& key) const
	{
		PythonLoader obj;

		return getItem(key, obj) ? obj.getString() : stdx::EmptyString();
	}
	int getKeys(vector<string>& vec) const
	{
		int sz = -1;
		PythonLoader obj = PyDict_Keys(handle);

		if (obj.canUse())
		{
			sz = obj.size();

			for (int i = 0; i < sz; i++)
			{
				vec.push_back(obj.getItem(i));
			}
		}

		return sz;
	}
	bool getItem(const string& key, int& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(key, obj));

		val = obj.getInteger();

		return true;
	}
	bool getItem(const string& key, bool& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(key, obj));

		val = obj.getBoolean();

		return true;
	}
	bool getItem(const string& key, double& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(key, obj));

		val = obj.getDouble();

		return true;
	}
	bool getItem(const string& key, string& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(key, obj));

		val = obj.getString();

		return true;
	}
	bool getItem(const string& key, SmartBuffer& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getItem(key, obj));

		val = obj.getBuffer();

		return true;
	}
	bool getItem(const string& key, PythonLoader& val) const
	{
		val = PyDict_GetItemString(handle, key.c_str());

		return val.addRef();
	}
	bool setItem(const string& key, int val)
	{
		return setItem(key, PyInt_FromLong(val));
	}
	bool setItem(const string& key, bool val)
	{
		return setItem(key, PyBool_FromLong(val ? 1 : 0));
	}
	bool setItem(const string& key, double val)
	{
		return setItem(key, PyFloat_FromDouble(val));
	}
	bool setItem(const string& key, const string& val)
	{
		return setItem(key, val.c_str(), val.length());
	}
	bool setItem(const string& key, const char* val, int sz = 0)
	{
		if (sz <= 0) sz = strlen(val);

		return setItem(key, PyByteArray_FromStringAndSize(val, sz));
	}

public:
	string getVariable(const string& name) const
	{
		string val;

		return getVariable(name, val) ?  val : stdx::EmptyString();
	}
	bool getVariable(const string& name, int& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getVariable(name, obj));

		val = obj.getInteger();

		return true;
	}
	bool getVariable(const string& name, bool& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getVariable(name, obj));

		val = obj.getBoolean();
		
		return true;
	}
	bool getVariable(const string& name, double& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getVariable(name, obj));

		val = obj.getDouble();

		return true;
	}
	bool getVariable(const string& name, string& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getVariable(name, obj));

		val = obj.getString();

		return true;
	}
	bool getVariable(const string& name, SmartBuffer& val) const
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(getVariable(name, obj));

		val = obj.getBuffer();

		return true;
	}
	bool getVariable(const string& name, PythonLoader& obj) const
	{
		obj = getObject(name);

		return obj.canUse();
	}
	bool setVariable(const string& name, int val)
	{
		return setVariable(name, PyInt_FromLong(val));
	}
	bool setVariable(const string& name, bool val)
	{
		return setVariable(name, PyBool_FromLong(val ? 1 : 0));
	}
	bool setVariable(const string& name, double val)
	{
		return setVariable(name, PyFloat_FromDouble(val));
	}
	bool setVariable(const string& name, const string& val)
	{
		return setVariable(name, val.c_str(), val.length());
	}
	bool setVariable(const string& name, const char* val, int sz = 0)
	{
		if (sz <= 0) sz = strlen(val);

		return setVariable(name, PyBytes_FromStringAndSize(val, sz));
	}
	bool call(const string& name, const ParamVector& vec, bool& res)
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(call(name, vec, obj));

		res = obj.getBoolean();

		return true;
	}
	bool call(const string& name, const ParamVector& vec, int& res)
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(call(name, vec, obj));

		res = obj.getInteger();

		return true;
	}
	bool call(const string& name, const ParamVector& vec, double& res)
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(call(name, vec, obj));

		res = obj.getDouble();

		return true;
	}
	bool call(const string& name, const ParamVector& vec, string& res)
	{
		PythonLoader obj;

		CHECK_FALSE_RETURN(call(name, vec, obj));

		res = obj.getString();

		return true;
	}
	bool call(const string& name, const ParamVector& vec, PythonLoader& res)
	{
		PythonLoader func;
		PythonLoader args = PyTuple_New(vec.size());

		CHECK_FALSE_RETURN(args.canUse() && getVariable(name, func));

		for (int i = 0; i < vec.size(); i++)
		{
			int type = vec.type(i);
			PythonHandle obj = Py_None;

			if (type == eINT)
			{
				obj = PyInt_FromLong(vec.asLong(i));
			}
			else if (type == eBOOL)
			{
				obj = PyBool_FromLong(vec.asLong(i));
			}
			else if (type == eDOUBLE)
			{
				obj = PyFloat_FromDouble(vec.asDouble(i));
			}
			else if (type == eSTRING)
			{
				obj = PyString_FromString(vec.data(i).c_str());
			}
			else if (type == eBUFFER)
			{
				const string& msg = vec.data(i);

				obj = PyBytes_FromStringAndSize(msg.c_str(), msg.length());
			}

			PyTuple_SetItem(args.getHandle(), i, obj);
		}

		res = PyEval_CallObject(func.getHandle(), args.getHandle());

		return res.canUse();
	}
};

typedef PythonLoader PythonObject;

/////////////////////////////////////////////////////////////////////
#endif